/**
 * 
 */
package manipulator;

/**
 * @author nquangtrung
 *
 */
public class BitSequence {
	private int currentBitOffset;
	private byte[] data;
	private int capacity;

	public BitSequence(byte[] data) {
		this.fromData(data);
		this.setCurrentOffset(0);
	}
	public BitSequence(int capacity) {
		this.setCapacity(capacity);
		this.setCurrentOffset(0);
	}
	
	public void fromData(byte[] data) {
		
	}
	public byte[] getArray() {
		return data;
	}
	public int byteSize() {
		return this.getCurrentOffset() / 8 + 1;
	}
	public byte nextBits(int size) {
		byte b = lookBits(size); 
		this.moveCurrentOffset(size);
		return b;
	}
	public byte lookBits(int size) {
		byte x = 0;
		
		if (size <= 8) {
			int currentByte = this.getCurrentOffset() / 8;
			int bitOffset = this.getCurrentOffset() % 8;
			
			int blank = 8 - bitOffset;
			byte b = this.getDataElement(currentByte);
			if (blank < size) {
				x |= extractBits(b, 0, blank - 1) << bitOffset;
				b = this.getDataElement(currentByte + 1);
				x |= extractBits(b,  8 - size + blank, 7) >> blank;
			} else {
				x |= extractBits(b, blank - size, blank - 1) << bitOffset;
			}
			x = (byte) (x >> (8 - size));
		}
		
		return x;
	}
	public int lookInt(int size) {
		int b = nextInt(size);
		this.moveCurrentOffset(-size);
		return b;
	}
	
	public int nextInt(int size) {
		int x = 0;
		if (size <= 32) {
			while (size > 8) {
				x = x * 256 + nextBits(8);
				size -= 8;
				// System.out.printf("\n%4x", x);
			}
			x = x * 256 + (nextBits(8) << (8 - size));
			x = x >> (8 - size);
		}
		return x;
	}
	public void stuff(int x, int size) {
		int n = Math.abs(x);
		// make 4 bytes
		byte[] b = new byte[4];
		for (int i = 0; i < 4; i++) {
			b[i] = (byte)(n % 256);
			n = n / 256;
		}
		// stuff each of the byte
		for (int i = 4; i > 0; i--) {
			if (i == size / 8) {
				int s = (size % 8 != 0) ? size % 8 : 8;
				stuff(b[i - 1], s);
				size -= s;
			}
		}
	}
	public int maxSize() {
		return this.getCapacity() * 8;
	}
	public boolean isFull() {
		return (this.getCurrentOffset() == this.maxSize());
	}
	public int bitLeft() {
		return this.maxSize() - this.getCurrentOffset();
	}
	
	public void stuff(byte b, int size) {
		if (size <= 8) {
			int currentByte = this.getCurrentOffset() / 8;
			int bitOffset = this.getCurrentOffset() % 8;
			
			// bitwise operator can work with int only
			// so c holds the value of b as an int with 
			// b's bit after a series of zero
			int c = extractBits(((int) b), 8 - size, 8);
			int blank = 8 - bitOffset;
			if (blank < size) {
				orDataElement(currentByte, c >> bitOffset);
				// the rest of those bits
				orDataElement(currentByte + 1, c << blank);
			} else {
				orDataElement(currentByte, c >> bitOffset);
			}
			this.moveCurrentOffset(size);
		} else {
			// Throw byte size mismatch exception
		}
	}
	public int extractBits(int c, int start, int end) {
		int removedTail = (c >> start) << (start);
		int removedHead = (removedTail << 32 - end) >>> (32 - end);
		return removedHead;
	}
	public int extractBits(byte c, int start, int end) {
		int removedTail = (c >> start) << (start);
		int removedHead = (removedTail << 8 - end) >>> (8 - end);
		return removedHead;
	}
	
	protected void andDataElement(int index, int value) {
		this.data[index] &= value; 
	}
	protected void orDataElement(int index, int value) {
		this.data[index] |= value; 
	}
	protected void setDataElement(int index, byte value) {
		this.data[index] = value;
	}
	public byte getDataElement(int index) {
		return this.data[index];	
	}
	
	public void extendCapacity(int capacity) {
		
	}
	
	/**
	 * @return the capacity
	 */
	public int getCapacity() {
		return capacity;
	}
	
	protected void moveCurrentOffset(int step) {
		this.setCurrentOffset(this.getCurrentOffset() + step);
	}
	/**
	 * @param capacity the capacity to set
	 */
	public void setCapacity(int capacity) {
		if (data == null) {
			this.capacity = capacity;
			data = new byte[capacity];
		}
	}
	/**
	 * @return the currentOffset
	 */
	public int getCurrentOffset() {
		return currentBitOffset;
	}
	/**
	 * @param currentOffset the currentOffset to set
	 */
	public void setCurrentOffset(int currentOffset) {
		this.currentBitOffset = currentOffset;
	}
}

